# 指定文件编码为UTF-8
# coding: utf-8

"""
weight_init_activation_histogram.py 功能说明：
1. 可视化不同权重初始化和激活函数对神经网络激活值分布的影响
2. 模拟5层神经网络的前向传播过程
3. 比较不同初始化方法（标准差为1、0.01、Xavier、He初始化）的效果
4. 支持三种激活函数（Sigmoid、ReLU、Tanh）的实验
5. 通过直方图展示各层激活值的分布变化

关键点：
- 展示深度神经网络中的梯度消失/爆炸问题
- 验证不同初始化方法对激活值分布的影响
- 可视化各层激活值分布的变化趋势
- 帮助理解Xavier和He初始化的必要性
"""

# 导入NumPy数值计算库
import numpy as np
# 导入matplotlib绘图库
import matplotlib.pyplot as plt

# 定义Sigmoid激活函数
def sigmoid(x):
    return 1 / (1 + np.exp(-x))

# 定义ReLU激活函数
def ReLU(x):
    return np.maximum(0, x)

# 定义Tanh激活函数
def tanh(x):
    return np.tanh(x)

# 生成输入数据：1000个样本，每个样本100维
input_data = np.random.randn(1000, 100)
# 每层神经元数量
node_num = 100
# 网络深度（5层隐藏层）
hidden_layer_size = 5
# 保存各层激活值的字典
activations = {}

# 初始化输入数据
x = input_data

# 模拟神经网络前向传播过程
for i in range(hidden_layer_size):
    # 如果不是第一层，使用上一层的输出作为当前层输入
    if i != 0:
        x = activations[i-1]

    # 权重初始化实验（取消注释切换不同初始化方法）
    w = np.random.randn(node_num, node_num) * 1  # 标准差为1
    # w = np.random.randn(node_num, node_num) * 0.01  # 标准差为0.01
    # w = np.random.randn(node_num, node_num) * np.sqrt(1.0 / node_num)  # Xavier初始化
    # w = np.random.randn(node_num, node_num) * np.sqrt(2.0 / node_num)  # He初始化

    # 计算加权和
    a = np.dot(x, w)

    # 激活函数实验（取消注释切换不同激活函数）
    z = sigmoid(a)  # 使用Sigmoid激活函数
    # z = ReLU(a)    # 使用ReLU激活函数
    # z = tanh(a)    # 使用Tanh激活函数

    # 保存当前层激活值
    activations[i] = z

# 绘制各层激活值分布直方图
for i, a in activations.items():
    # 创建子图（1行5列）
    plt.subplot(1, len(activations), i+1)
    # 设置子图标题
    plt.title(str(i+1) + "-layer")
    # 除第一层外，隐藏y轴刻度
    if i != 0:
        plt.yticks([], [])
    # 可以取消注释设置坐标轴范围
    # plt.xlim(0.1, 1)
    # plt.ylim(0, 7000)
    # 绘制直方图（30个bin，范围0-1）
    plt.hist(a.flatten(), 30, range=(0,1))
# 显示图形
plt.show()

"""
实验结果分析：
1. 使用标准差为1的初始化：
   - Sigmoid激活值会快速集中在0和1附近（梯度消失）
   - ReLU激活值会向右侧偏移（神经元死亡）

2. 使用标准差为0.01的初始化：
   - 激活值集中在中间区域，多层后信号减弱

3. 使用Xavier初始化（适合Sigmoid/Tanh）：
   - 保持激活值分布广度，避免梯度消失

4. 使用He初始化（适合ReLU）：
   - 更好地保持ReLU网络的激活值分布

注意事项：
1. 每次运行结果会有随机差异
2. 可以尝试调整网络深度观察效果
3. 不同激活函数需要匹配对应的初始化方法
4. 直方图范围可根据需要调整
"""
